package bloodfang;

import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

public class Bloodfang {
	
	public static void main(String[] args) {
		System.out.println("Bloodfang started...");
		new Bloodfang(true);
	}
	
	public Stats stats;
	public Attacks attacks;
	public Settings settings;
	public Cycle cycle;
	public double dps;
	
	public HashMap<String,Double> ep = new HashMap<String,Double>();
	public HashMap<String,double[]> ep_procs = new HashMap<String,double[]>();
	HashMap<String,StatBlock> staticItemStats = new HashMap<String,StatBlock>();
	
	public StatBlock getGearStatsIndz(){
		StatBlock gearStats = new StatBlock();
		gearStats.setStat(Stat.str, 80);
		gearStats.setStat(Stat.agi, 18643);
		gearStats.setStat(Stat.cri, 4411);
		gearStats.setStat(Stat.hst, 10281);
		gearStats.setStat(Stat.mst, 6543);
		gearStats.setStat(Stat.mul, 3300);
		gearStats.setStat(Stat.ver, 2900);
		return gearStats;
	}
	
	public Bloodfang(){
		
	}
	
	public Bloodfang(boolean test){
		fillStaticItemStats();
		StatBlock gearStats = new StatBlock();
		gearStats.setStat(Stat.str, 20);
		gearStats.setStat(Stat.agi, 608);
		gearStats.setStat(Stat.cri, 370);
		gearStats.setStat(Stat.hst, 350);
		gearStats.setStat(Stat.mst, 280);
		gearStats.setStat(Stat.mul, 100);
		gearStats.setStat(Stat.ver, 100);
		
		//gearStats = getGearStatsIndz();
		
		Race race = Race.dwarf;
		
		Weapon mh = new Weapon(135.8,2.6,WeaponType.SWORD);
		Weapon oh = new Weapon(135.8,2.6,WeaponType.SWORD);
//		mh = new Weapon(135.8,1.8,WeaponType.DAGGER); // for assassination and subtlety
//		oh = new Weapon(135.8,1.8,WeaponType.DAGGER); // for assassination and subtlety

		
		String tal = "2211021";
		HashSet<String> gly = new HashSet<String>();
		gly.add("disappearance");
		
		EnumSet<SetBonus> setB = EnumSet.of(SetBonus.T17_2PC,SetBonus.T17_4PC);
		//setB = EnumSet.noneOf(SetBonus.class);
		
		HashSet<ProcInfo> procs = new HashSet<ProcInfo>();
//		procs.add(new ProcInfo("shado_pan_assault",2));
//		procs.add(new ProcInfo("lfr_renatakis_soul_charm",2));
//		procs.add(new ProcInfo("heroic_thunder_bad_juju"));
//		procs.add(new ProcInfo("heroic_thunder_renatakis_soul_charm"));
//		procs.add(new ProcInfo("haromms_talisman",0));
//		procs.add(new ProcInfo("sigil_of_rampage",0));
//		procs.add(new ProcInfo("heroic_war_assurance_of_consequence",0));
//		procs.add(new ProcInfo("heroic_war_haromms_talisman",0));
//		procs.add(new ProcInfo("heroic_war_ticking_ebon_detonator",2));
//		gearStats.add(this.staticItemStats.get("heroic_war_ticking_ebon_detonator"));
//		procs.add(new ProcInfo("capacitive_primal_diamond"));
//		procs.add(new ProcInfo("fury_of_xuen"));
//		procs.add(new ProcInfo("mh_dancing_steel"));
//		procs.add(new ProcInfo("oh_dancing_steel"));
//		procs.add(new ProcInfo("mh_mark_of_the_frostwolf"));
//		procs.add(new ProcInfo("oh_mark_of_the_frostwolf"));
//		procs.add(new ProcInfo("mh_mark_of_the_shattered_hand"));
//		procs.add(new ProcInfo("oh_mark_of_the_shattered_hand"));
		
		
		settings = new Settings();
		
		stats = new Stats(settings, gearStats, race, mh, oh, tal, gly, EnumSet.allOf(RaidBuff.class), setB, procs);
		attacks = new Attacks(stats);
		
		Spec spec = Spec.COMBAT;
		
//		long start = System.currentTimeMillis();
		
		cycle = Cycle.createCycle(spec, stats, attacks);
		
		long start = System.currentTimeMillis();
		dps = cycle.get_dps();
		cycle.show_proc_uptimes();
		cycle.show_aps_breakdown();
		cycle.show_dps_breakdown();
//		
		ep = calc_ep();
//
//		ep_procs = calc_ep_procs();
//		System.out.println("");
//		System.out.println("Trinket Ranking:");
//		this.show_trinket_ranking(ep_procs);
//		
		calc_ep_setbonus();
//		calc_ep_races();
//		calc_ep_dps();
//		calc_ep_weapon_type("mh");
		
		System.out.println("DPS: " + dps);
		long end = System.currentTimeMillis();
		System.out.println("Calculation Time: " + (end-start)/1000.);
	}
	
	public HashMap<String,Double> calc_ep(){
		HashMap<String,Double> ep_values = new HashMap<String,Double>();
		double delta = 1.;
		Stat normalize_stat = Stat.ap;
		stats.setEpStats(new StatBlock(normalize_stat, delta));
		double normalize_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock());
		System.out.format("EP %-15s%12.5f%n", normalize_stat, (normalize_dps - dps) / (normalize_dps - dps));
		ep_values.put(normalize_stat.name(), (normalize_dps - dps) / (normalize_dps - dps));
		for(Stat s : new Stat[]{Stat.ap,Stat.agi,Stat.str,Stat.mst,Stat.hst,Stat.cri,Stat.mul,Stat.ver}){
			if(s == normalize_stat) continue;
			stats.setEpStats(new StatBlock(s, delta));
			double ep_dps = cycle.get_dps();
//			System.out.println(s+ " " + ep_dps + " " + dps);
			stats.setEpStats(new StatBlock());
			double ep = (ep_dps - dps) / (normalize_dps - dps);
			System.out.format("EP %-15s%12.5f%n", s, ep);
			ep_values.put(s.name(), ep);
		}
		return ep_values;
	}
	
	public void calc_ep_setbonus(){
		double delta = 1.;
		Stat normalize_stat = Stat.ap;
		stats.setEpStats(new StatBlock(normalize_stat, delta));
		double normalize_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock());
		for(SetBonus s : SetBonus.values()){
			stats.toggleSetBonus(s);
			double ep_dps = cycle.get_dps();
			stats.toggleSetBonus(s);
			double ep = Math.abs(ep_dps - dps) / (normalize_dps - dps);
			System.out.format("EP %-15s%12.5f%n", s, ep);
		}
	}
	
	public HashMap<String, double[]> calc_ep_procs(){
		HashMap<String,double[]> ep_values = new HashMap<String,double[]>();
		double delta = 1.;
		Stat normalize_stat = Stat.ap;
		@SuppressWarnings("unchecked")
		HashSet<ProcInfo> currentProcs = (HashSet<ProcInfo>) stats.getProcs().clone();
		stats.getProcs().clear();
		double procs_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock(normalize_stat, delta));
		double procs_normalize_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock());
		for(ProcData p : stats.getProcData().getProcData()){
			if(p.behavior.scaling){
				int[] lvls = new int[]{0,1,2};
				double eps[] = new double[lvls.length];
				//System.out.print("EP "+ p.name + ": ");
				for(int lvl : lvls){
					ProcInfo pi = new ProcInfo(p.name,lvl);
					stats.getProcs().add(pi);
					double ep_dps = cycle.get_dps();
					stats.getProcs().remove(pi);
					double ep = Math.abs(ep_dps - procs_dps) / (procs_normalize_dps - procs_dps);
					eps[lvl] = ep;
					//System.out.format("%.2f ", ep);
				}
				ep_values.put(p.name, eps);
				//System.out.println("");
			}else{
				ProcInfo pi = new ProcInfo(p.name,0);
				stats.getProcs().add(pi);
				double ep_dps = cycle.get_dps();
				stats.getProcs().remove(pi);
				double ep = Math.abs(ep_dps - procs_dps) / (procs_normalize_dps - procs_dps);
				//System.out.println("EP "+ p.name +": " + ep);
				ep_values.put(p.name, new double[]{ep});
			}
		}
		stats.setProcs(currentProcs);
		EPComparator comp = new EPComparator(ep_values);
		TreeMap<String, double[]> ep_procs_sorted = new TreeMap<String, double[]>(comp);
		ep_procs_sorted.putAll(ep_values);
		for (Entry<String, double[]> entry : ep_procs_sorted.entrySet()){
			System.out.format("%-40s%-1s%n", entry.getKey(), Arrays.toString(entry.getValue()));
		}
		return ep_values;
	}
	
	@SuppressWarnings("unchecked")
	public void show_trinket_ranking(HashMap<String,double[]> ep_values){
		for(String name : ((HashMap<String, Double>) ep_values.clone()).keySet()){
			double[] val = ep_values.get(name);
			for(int u = 0; u < val.length; u++){
				String _name = name;
				if(u > 0) _name += "/"+u;
				if(this.staticItemStats.containsKey(_name)){
					val[u] += getValue(this.staticItemStats.get(_name));
				}
			}
			ep_values.put(name, val);
		}
		EPComparator comp = new EPComparator(ep_values);
		TreeMap<String, double[]> ep_procs_sorted = new TreeMap<String, double[]>(comp);
		ep_procs_sorted.putAll(ep_values);
		for (Entry<String, double[]> entry : ep_procs_sorted.entrySet()){
			System.out.format("%-40s%-1s%n", entry.getKey(), Arrays.toString(entry.getValue()));
		}
	}
	
	public void calc_ep_races(){
		double delta = 1.;
		Stat normalize_stat = Stat.ap;
		Race currentRace = stats.getRace();
		stats.setRace(Race.none);
		double none_race_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock(normalize_stat, delta));
		double none_race_normalize_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock());
		for(Race r : Race.values()){
			if(r == Race.none) continue;
			stats.setRace(r);
			double ep_dps = cycle.get_dps();
			stats.setRace(Race.none);
			double ep = Math.abs(ep_dps - none_race_dps) / (none_race_normalize_dps - none_race_dps);
			System.out.format("EP %-15s%12.5f%n", r.name(), ep);
		}
		stats.setRace(currentRace);
	}
	
	public void calc_ep_dps(){
		double delta = 1.;
		Stat normalize_stat = Stat.ap;
		stats.setEpStats(new StatBlock(normalize_stat, delta));
		double normalize_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock());
		
		stats.getMh().setDps(stats.getMh().getDps() + 1.);
		double ep_dps = cycle.get_dps();
		stats.getMh().setDps(stats.getMh().getDps() - 1.);
		double ep = Math.abs(ep_dps - dps) / (normalize_dps - dps);
		System.out.println("EP "+ "mh_dps" +": " + ep);
		
		stats.getOh().setDps(stats.getOh().getDps() + 1.);
		ep_dps = cycle.get_dps();
		stats.getOh().setDps(stats.getOh().getDps() - 1.);
		ep = Math.abs(ep_dps - dps) / (normalize_dps - dps);
		System.out.println("EP "+ "oh_dps" +": " + ep);
	}
	
	public void calc_ep_weapon_type(String hand){
		double delta = 1.;
		Stat normalize_stat = Stat.ap;
		stats.setEpStats(new StatBlock(normalize_stat, delta));
		double normalize_dps = cycle.get_dps();
		stats.setEpStats(new StatBlock());
		
		Weapon w = null;
		if(hand.equals("mh"))
			w = stats.getMh();
		if(hand.equals("oh"))
			w = stats.getOh();
		if(w == null){
			System.err.println("Error: input must be 'mh' or 'oh'");
			return;
		}
		double currentSpeed = stats.getOh().getSpeed();
		WeaponType currentType = stats.getOh().getType();
		for(WeaponType type : WeaponType.values()){
			w.setType(type);
			w.setSpeed(type.getDefaultSpeed());
			double ep_dps = cycle.get_dps();
			double ep = (ep_dps - dps) / (normalize_dps - dps);
			System.out.println("EP "+ hand + "_"+type.name() +": " + ep);
		}
		w.setSpeed(currentSpeed);
		w.setType(currentType);
	}
	
	private class EPComparator implements Comparator<Object> {
		
		Object object;

	    public EPComparator(Object object) {
	        this.object = object;
	    }
		
		@Override
		public int compare(Object key1, Object key2) {
			double elem1 = 0.;
			double elem2 = 0.;
			if(object instanceof Map){
				Object item1 = ((Map<?, ?>) object).get(key1);
				Object item2 = ((Map<?, ?>) object).get(key2);
				if(item1 instanceof double[]){
					elem1 = ((double[])item1)[0];
				}else{
					elem1 = (double) item1;
				}
				if(item2 instanceof double[]){
					elem2 = ((double[])item2)[0];
				}else{
					elem2 = (double) item2;
				}
			}
			if(elem1 > elem2)
				return -1;
			else
				return 1;
		}
		
	}
	
	private void fillStaticItemStats(){
		// every spec
		staticItemStats.put("heroic_war_haromms_talisman", new StatBlock(Stat.ver,2000));
		staticItemStats.put("discipline_of_xuen", new StatBlock(Stat.agi,1152));
		staticItemStats.put("timeless_discipline_of_xuen", new StatBlock(Stat.agi,1657));
		staticItemStats.put("heroic_thunder_talisman_of_bloodlust", new StatBlock(Stat.agi,1752));
		staticItemStats.put("heroic_thunder_talisman_of_bloodlust/1", new StatBlock(Stat.agi,1818));
		staticItemStats.put("heroic_thunder_talisman_of_bloodlust/2", new StatBlock(Stat.agi,1887));
		staticItemStats.put("heroic_talisman_of_bloodlust", new StatBlock(Stat.agi,1657));
		staticItemStats.put("thunder_talisman_of_bloodlust", new StatBlock(Stat.agi,1552));
		staticItemStats.put("talisman_of_bloodlust", new StatBlock(Stat.agi,1467));
		staticItemStats.put("lfr_talisman_of_bloodlust", new StatBlock(Stat.agi,1218));
		
		// IF combat or subtlety?? is sub primarly haste?
		staticItemStats.put("heroic_war_ticking_ebon_detonator", new StatBlock(Stat.mst,0.6*2339,Stat.hst,0.4*2339));
		staticItemStats.put("heroic_ticking_ebon_detonator", new StatBlock(Stat.mst,0.6*2211,Stat.hst,0.4*2211));
		staticItemStats.put("war_ticking_ebon_detonator", new StatBlock(Stat.mst,0.6*2072,Stat.hst,0.4*2072));
		staticItemStats.put("ticking_ebon_detonator", new StatBlock(Stat.mst,0.6*1959,Stat.hst,0.4*1959));
		staticItemStats.put("flex_ticking_ebon_detonator", new StatBlock(Stat.mst,0.6*1735,Stat.hst,0.4*1735));
		staticItemStats.put("lfr_ticking_ebon_detonator", new StatBlock(Stat.mst,0.6*1552,Stat.hst,0.4*1552));
		
		staticItemStats.put("heroic_thunder_renatakis_soul_charm", new StatBlock(Stat.ver,(int) (0.6 * 1752), Stat.hst, (int) (0.4 * 1752)));
		staticItemStats.put("heroic_renatakis_soul_charm", new StatBlock(Stat.ver,(int) (0.6 * 1657), Stat.hst, (int) (0.4 * 1657)));
		staticItemStats.put("thunder_renatakis_soul_charm", new StatBlock(Stat.ver,(int) (0.6 * 1552), Stat.hst, (int) (0.4 * 1552)));
		staticItemStats.put("renatakis_soul_charm", new StatBlock(Stat.ver,(int) (0.6 * 1467), Stat.hst, (int) (0.4 * 1467)));
		staticItemStats.put("lfr_renatakis_soul_charm", new StatBlock(Stat.ver,(int) (0.6 * 1218), Stat.hst, (int) (0.4 * 1218)));
		
		staticItemStats.put("heroic_thunder_bad_juju", new StatBlock(Stat.mst,0.6*1752,Stat.hst,0.4*1752));
		staticItemStats.put("heroic_bad_juju", new StatBlock(Stat.mst,0.6*1657,Stat.hst,0.4*1657));
		staticItemStats.put("thunder_bad_juju", new StatBlock(Stat.mst,0.6*1552,Stat.hst,0.4*1552));
		staticItemStats.put("bad_juju", new StatBlock(Stat.mst,0.6*1467,Stat.hst,0.4*1467));
		staticItemStats.put("lfr_bad_juju", new StatBlock(Stat.mst,0.6*1218,Stat.hst,0.4*1218));
		
		staticItemStats.put("shado_pan_assault", new StatBlock(Stat.mul,0.6*1467,Stat.hst,0.4*1467));
		
		// IF Assa
//		staticItemStats.put("heroic_war_ticking_ebon_detonator", new StatBlock(Stat.mst,2339));
//		staticItemStats.put("heroic_ticking_ebon_detonator", new StatBlock(Stat.mst,2211));
//		staticItemStats.put("war_ticking_ebon_detonator", new StatBlock(Stat.mst,2072));
//		staticItemStats.put("ticking_ebon_detonator", new StatBlock(Stat.mst,1959));
//		staticItemStats.put("flex_ticking_ebon_detonator", new StatBlock(Stat.mst,1735));
//		staticItemStats.put("lfr_ticking_ebon_detonator", new StatBlock(Stat.mst,1552));
//		
//		staticItemStats.put("heroic_thunder_renatakis_soul_charm", new StatBlock(Stat.exp,(int) (0.6 * 1752), Stat.mst, (int) (0.4 * 1752)));
//		staticItemStats.put("heroic_renatakis_soul_charm", new StatBlock(Stat.exp,(int) (0.6 * 1657), Stat.mst, (int) (0.4 * 1657)));
//		staticItemStats.put("thunder_renatakis_soul_charm", new StatBlock(Stat.exp,(int) (0.6 * 1552), Stat.mst, (int) (0.4 * 1552)));
//		staticItemStats.put("renatakis_soul_charm", new StatBlock(Stat.exp,(int) (0.6 * 1467), Stat.mst, (int) (0.4 * 1467)));
//		staticItemStats.put("lfr_renatakis_soul_charm", new StatBlock(Stat.exp,(int) (0.6 * 1218), Stat.mst, (int) (0.4 * 1218)));
//		
//		staticItemStats.put("heroic_thunder_bad_juju", new StatBlock(Stat.mst,1752));
//		staticItemStats.put("heroic_bad_juju", new StatBlock(Stat.mst,1657));
//		staticItemStats.put("thunder_bad_juju", new StatBlock(Stat.mst,1552));
//		staticItemStats.put("bad_juju", new StatBlock(Stat.mst,1467));
//		staticItemStats.put("lfr_bad_juju", new StatBlock(Stat.mst,1218));
//		
//		staticItemStats.put("shado_pan_assault", new StatBlock(Stat.hit,0.6*1467,Stat.mst,0.4*1467));
		
	}
	
	private double getValue(StatBlock sb){
		double ret = 0.;
		for(Stat stat : Stat.values()){
			if(sb.getStat(stat) > 0){
				ret += sb.getStat(stat) * ep.get(stat.name());
			}
		}
		return ret;
	}
	
}
